package msrc

import (
	"context"
	"os"
	"os/signal"
	"strconv"
	"sync"

	"github.com/pkg/errors"
	"github.com/sirupsen/logrus"
	"gitlab.systems-fd.com/packages/golang/helpers/h"
	"golang.org/x/sync/errgroup"
)

// multiServiceServer имплементирует логику сервера для MultiServiceInterface.
// Под капотом использует функционал signal.Notify для реализации правильной
// остановки по полученному из системы сигналу завершения работы.
type multiServiceServer struct {
	services           map[string]ServiceInterface
	servicesComplete   map[string]bool
	servicesCompleteMX sync.Mutex

	serviceStopFunc      *h.SyncValueStruct[context.CancelFunc]
	servicesIsWorkMx     *sync.Mutex
	isServicesRun        *h.SyncValueStruct[bool]
	isShutdownInProgress *h.SyncValueStruct[bool]
	logger               *logrus.Entry
}

// MultiService реализует конструктор сервиса MultiServiceInterface.
// Данный конструктор принимает на вход карту сервисов, которые необходимо
// запускать основному серверу. Данные сервисы будут запускаться при
// помощи функции Run().
//
// Вторым аргументом на вход передается livenessPort, который является
// не обязательным параметром. Данный параметр позволяет подключить к
// сервису дополнительный функционал проверки Liveness Probe для
// Kubernetes.
func MultiService(
	services map[string]ServiceInterface,
	livenessPort ...uint16,
) MultiServiceInterface {
	mainService := &multiServiceServer{
		services:             map[string]ServiceInterface{},
		servicesComplete:     map[string]bool{},
		servicesCompleteMX:   sync.Mutex{},
		serviceStopFunc:      h.SyncValue[context.CancelFunc](func() {}),
		servicesIsWorkMx:     &sync.Mutex{},
		isServicesRun:        h.SyncValue[bool](false),
		isShutdownInProgress: h.SyncValue[bool](false),
		logger:               logrus.WithField("prefix", `MultiService`),
	}

	if 0 != len(livenessPort) {
		if livenessPort[0] > 0 {
			livenessService := newLivenessService(strconv.FormatUint(uint64(livenessPort[0]), 10), mainService)

			services[`LivenessService`] = livenessService
		}
	}

	mainService.services = services

	return mainService
}

// Run выполняет запуск основного сервиса
func (m *multiServiceServer) Run() error {
	if m.isServicesRun.Get() {
		return nil
	}

	m.isServicesRun.Set(true)
	defer m.isServicesRun.Set(false)

	defer func() {
		m.logger.
			WithField("code", 20010).
			Info("All services completely stopped")
	}()

	m.servicesIsWorkMx.Lock()
	defer m.servicesIsWorkMx.Unlock()

	ctx, cancel := context.WithCancel(context.Background())
	defer cancel()

	m.serviceStopFunc.Set(cancel)

	osSignals := make(chan os.Signal, 1)
	signal.Notify(osSignals, signals...)

	defer signal.Stop(osSignals)
	defer close(osSignals)

	g, wCtx := errgroup.WithContext(ctx)

	// Регистрируем подписку на сигнал завершения работы от системы или
	// от общего рабочего контекста.
	go func() {
		select {
		case <-wCtx.Done():
		case <-osSignals:
		}

		m.Stop()
	}()

	m.servicesCompleteMX.Lock()
	for i := range m.services {
		serviceName := i
		service := m.services[i]
		m.servicesComplete[serviceName] = false

		g.Go(func() error {
			if h.IsCtxDone(wCtx) {
				return nil
			}

			err := service.Run()

			m.servicesCompleteMX.Lock()
			m.servicesComplete[serviceName] = true
			m.servicesCompleteMX.Unlock()

			// Если завершение пришло по функции Stop, то игнорируем ошибки.
			if h.IsCtxDone(wCtx) {
				return nil
			}

			if nil == err {
				m.logger.WithFields(logrus.Fields{
					"code":    20200,
					"service": serviceName,
				}).Info("Service complete")

				return nil
			}

			m.logger.
				WithError(err).
				WithField("code", 50000).
				WithField("service", serviceName).
				Error(`Response error from service`)

			return errors.Wrap(err, `[`+serviceName+`] response error from service`)
		})
	}
	m.servicesCompleteMX.Unlock()

	err := g.Wait()
	if err == context.Canceled {
		return nil
	}

	if nil != err {
		return errors.Wrap(err, `[MultiService] received error from service`)
	}

	return nil
}

// Stop выполняет остановку основного сервиса
func (m *multiServiceServer) Stop() {
	if m.isShutdownInProgress.Get() {
		return
	}

	m.isShutdownInProgress.Set(true)
	defer m.isShutdownInProgress.Set(false)

	m.logger.
		WithField("code", 20004).
		Info("Initialized graceful shutdown")

	m.serviceStopFunc.Get()()

	var wg sync.WaitGroup
	wg.Add(len(m.services))

	for name, service := range m.services {
		go func(name string, service ServiceInterface, wg *sync.WaitGroup) {
			defer wg.Done()

			m.servicesCompleteMX.Lock()
			status := m.servicesComplete[name]
			m.servicesCompleteMX.Unlock()

			if status {
				return
			}

			m.logger.
				WithField("code", 20004).
				WithField("service", name).
				Debug(`Shutdown service`)

			service.GracefulShutdown()
		}(name, service, &wg)
	}

	wg.Wait()

	m.servicesIsWorkMx.Lock()
	defer m.servicesIsWorkMx.Unlock()

	m.logger.
		WithField("code", 20004).
		Info("Services gracefully shutdown")
}

// IsServicesRun проверяет, запущен ли сервер
func (m *multiServiceServer) IsServicesRun() bool {
	status := true
	for _, service := range m.services {
		status = status && service.IsStarted()
	}

	return status
}

// IsServicesAlive проверяет, остановлен ли хоть один из сервисов
func (m *multiServiceServer) IsServicesAlive() bool {
	status := true
	for _, service := range m.services {
		status = status && service.IsAlive()
	}

	return status
}
